---
title: ComponentConfig
---

import { ConfigPreview, PuckPreview } from "@/docs/components/Preview";
import { Drawer } from "@/puck";

# ComponentConfig

The configuration for each component defined in [`Config`](/docs/api-reference/configuration/config).

```tsx {3-10} copy
const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: {
          type: "text",
        },
      },
      render: ({ title }) => <h1>{title}</h1>,
    },
  },
};
```

## Params

| Param                                                    | Example                                         | Type     | Status   |
| -------------------------------------------------------- | ----------------------------------------------- | -------- | -------- |
| [`render()`](#renderprops)                               | `render: () => <div />`                         | Function | Required |
| [`fields`](#fields)                                      | `fields: { title: { type: "text"} }`            | Object   | -        |
| [`defaultProps`](#defaultprops)                          | `defaultProps: { title: "Hello, world" }`       | Object   | -        |
| [`inline`](#inline)                                      | `inline: true`                                  | Boolean  | -        |
| [`label`](#label)                                        | `label: "Heading Block"`                        | String   | -        |
| [`metadata`](#metadata)                                  | `metadata: {}`                                  | Object   | -        |
| [`permissions()`](#permissions)                          | `permissions: { delete: false }`                | Object   | -        |
| [`resolveData()`](#resolvedatadata-params)               | `resolveData: async ({ props }) => ({ props })` | Object   | -        |
| [`resolveFields()`](#resolvefieldsdata-params)           | `resolveFields: async ({ props }) => ({})`      | Object   | -        |
| [`resolvePermissions()`](#resolvepermissionsdata-params) | `resolvePermissions: async ({ props }) => ({})` | Object   | -        |

## Required params

### `render(props)`

A render function to render your component. Receives props as defined in `fields`, and some internal Puck props.

```tsx {4} copy
const config = {
  components: {
    HeadingBlock: {
      render: () => <h1>Hello, world</h1>,
    },
  },
};
```

#### Render props

| Arg                                             | Example       | Type     |
| ----------------------------------------------- | ------------- | -------- |
| [`id`](#id)                                     | `button-1234` | String   |
| [`puck.dragRef`](#puckdragref)                  | `null`        | Function |
| [`puck.isEditing`](#puckisediting)              | `false`       | Boolean  |
| [`puck.metadata`](/docs/api-reference/metadata) | `{}`          | Object   |
| [`puck.renderDropZone`](#puckrenderdropzone)    | `() => {}`    | Function |
| [`...props`](#props)                            | `{}`          | Object   |

##### `id`

A unique identifier for the component. Auto-generated by default.

##### `puck.dragRef`

A `ref` that tells Puck which element is draggable. Apply this to your components when using the [`inline` parameter](#inline) for advanced CSS layouts.

```tsx {5} /renderDropZone/1 copy
const config = {
  components: {
    Example: {
      inline: true,
      render: ({ puck: { dragRef } }) => {
        return <div ref={dragRef}>Hello, world</div>;
      },
    },
  },
};
```

##### `puck.isEditing`

A boolean describing whether or not this component is being rendered in the `<Puck>` component.

##### `puck.metadata`

An object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).

```tsx {5} /renderDropZone/1 copy
const config = {
  components: {
    Example: {
      inline: true,
      render: ({ text, puck: { metadata } }) => {
        return <div>Hello, {metadata.text || text}</div>;
      },
    },
  },
};
```

##### `puck.renderDropZone`

A render method that creates a [`<DropZone>`](/docs/api-reference/components/drop-zone) for creating nested components. Use this method instead of the `<DropZone>` when implementing React server components.

```tsx {5} /renderDropZone/1 copy
const config = {
  components: {
    Example: {
      render: ({ puck: { renderDropZone } }) => {
        return <div>{renderDropZone({ zone: "my-content" })}</div>;
      },
    },
  },
};
```

##### `...props`

The remaining props are provided by the user-defined [fields](#fields).

## Optional params

### `fields`

An object describing which [`Field`](/docs/api-reference/fields) to show for each prop passed to the component.

```tsx {4-8} copy showLineNumbers
const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: {
          type: "text",
        },
      },
      render: ({ title }) => <h1>{title}</h1>,
    },
  },
};
```

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    render: ({ title }) => {
      return <p style={{ margin: 0 }}>{title}</p>;
    },
  }}
/>

### `defaultProps`

Default props to apply to a new instance of the component.

```tsx {9} copy showLineNumbers
const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: {
          type: "text",
        },
      },
      defaultProps: { title: "Hello, world" },
      render: ({ title }) => <h1>{title}</h1>,
    },
  },
};
```

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    render: ({ title }) => {
      return <p style={{ margin: 0 }}>{title}</p>;
    },
  }}
/>

### `inline`

Render your component without a wrapping element. Use this to [create advanced CSS layouts](/docs/integrating-puck/multi-column-layouts#advanced-css-layouts). Defaults to `false`.

When `true`, you must to specify which item is draggable via the [`puck.dragRef` prop](#puckdragref).

```tsx {4-5} copy showLineNumbers
const config = {
  components: {
    HeadingBlock: {
      inline: true,
      render: ({ puck }) => <h1 ref={puck.dragRef}>Hello, World</h1>,
    },
  },
};
```

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    inline: true,
    render: ({ title, puck }) => {
      return (
        <p style={{ margin: 0 }} ref={puck.dragRef}>
          {title}
        </p>
      );
    },
  }}
/>

### `label`

A label to show when referring to your component within the Puck editor. Defaults to the key of your component.

```tsx {4} copy showLineNumbers
const config = {
  components: {
    HeadingBlock: {
      label: "Heading Block",
      render: () => <h1>Hello, World</h1>,
    },
  },
};
```

<PuckPreview config={{}} data={{ root: { props: {} }, content: [] }}>
  <Drawer>
    <Drawer.Item name="Heading Block" />
  </Drawer>
</PuckPreview>

### `metadata`

An object containing any additional values. Provided to their [`render`](#renderprops) and [`resolveData`](#resolvedatadata-params) functions.

Will be merged with the global `metadata` prop provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, overriding any keys with the same name.

```tsx {4-7}
const config = {
  components: {
    HeadingBlock: {
      metadata: {
        title: "Hello, world",
      },
      render: ({ puck }) => <h1>{puck.metadata.title}</h1>,
    },
  },
};
```

### `permissions`

Set the [permissions](/docs/api-reference/permissions) for all instances of a component to toggle functionality. Inherits [global permissions](/docs/api-reference/components/puck/#permissions).

```tsx {4-6} copy showLineNumbers
const config = {
  components: {
    HeadingBlock: {
      permissions: {
        delete: false, // Disable deletion of all HeadingBlock instances
      },
      render: () => <h1>Hello, World</h1>,
    },
  },
};
```

### `resolveData(data, params)`

Dynamically change the props and set fields as read-only. Supports asynchronous calls.

This function is triggered when [`<Puck>`](/docs/api-reference/components/puck) renders, when a field is changed, or when the [`resolveAllData` function](/docs/api-reference/functions/resolve-all-data) is called.

```tsx {9-14} copy filename="Example mapping 'title' to 'resolvedTitle'"
const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: {
          type: "text",
        },
      },
      resolveData: async ({ props }) => {
        return {
          props: { resolvedTitle: props.title },
          readOnly: { resolvedTitle: true },
        };
      },
      render: ({ resolvedTitle }) => <h1>{resolvedTitle}</h1>,
    },
  },
};
```

<ConfigPreview
  label='Try changing the "title" field'
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
      resolvedTitle: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    resolveData: ({ props }) => {
      return {
        props: { resolvedTitle: props.title },
        readOnly: { resolvedTitle: true },
      };
    },
    render: ({ resolvedTitle }) => {
      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;
    },

}}
/>

#### Args

| Prop     | Example                                                  | Type   |
| -------- | -------------------------------------------------------- | ------ |
| `data`   | `{ props: { title: "Hello, world" }, readOnly: {} }`     | Object |
| `params` | `{ changed: { title: true }, metadata: { foo: "bar" } }` | Object |

##### `data.props`

The current props for the component.

```tsx copy /props/1,3
const resolveData = async ({ props }) => {
  return {
    props: { resolvedTitle: props.title },
  };
};
```

##### `data.readOnly`

The fields currently set to read-only for this component.

##### `params.changed`

An object describing which props have changed on this component since the last time `resolveData` was called.

```tsx copy {2-4} /changed/1 filename="Example only updating 'resolvedTitle' when 'title' changes"
const resolveData = async ({ props }, { changed }) => {
  if (!changed.title) {
    return { props };
  }

  return {
    props: { resolvedTitle: props.title },
  };
};
```

##### `params.lastData`

The data object from the previous run of this function.

##### `params.metadata`

An object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).

```tsx copy {2-4} /changed/1 filename="Example only updating 'resolvedTitle' when 'title' changes"
const resolveData = async ({ props }, { metadata }) => {
  return {
    props: { title: metadata.title || props.title },
  };
};
```

##### `params.trigger`

The event that triggered this resolveData call. One of:

- `insert` - the component was inserted
- `replace` - the component was replaced, such as when a field value is changed
- `load` - the Puck editor was loaded
- `force` - an execution was forced via the [`resolveAllData` function](/docs/api-reference/functions/resolve-all-data)

#### Returns

| Prop   | Example                                              | Type   |
| ------ | ---------------------------------------------------- | ------ |
| `data` | `{ props: { title: "Hello, world" }, readOnly: {} }` | Object |

##### `data.props`

A partial props object containing modified props. Will be spread into the other props.

```tsx copy {3} filename="Example only updating resolvedTitle when title changes"
const resolveData = async ({ props }) => {
  return {
    props: { resolvedTitle: props.title },
  };
};
```

##### `data.readOnly`

A partial object describing fields to set as readonly. Will be spread into the existing readOnly state.

```tsx copy {4} filename="Example only updating resolvedTitle when title changes"
const resolveData = async ({ props }) => {
  return {
    props: { resolvedTitle: props.title },
    readOnly: { resolvedTitle: true }, // Make the `resolvedTitle` field read-only
  };
};
```

### `resolveFields(data, params)`

Dynamically set the fields for this component. Supports asynchronous calls.

This function is triggered when the component data changes.

```tsx {4-25} copy filename="Example changing one field based on another"
const config = {
  components: {
    MyComponent: {
      resolveFields: (data) => {
        const fields = {
          drink: {
            type: "radio",
            options: [
              { label: "Water", value: "water" },
              { label: "Orange juice", value: "orange-juice" },
            ],
          },
        };

        if (data.props.drink === "water") {
          return {
            ...fields,
            waterType: {
              // ... Define field
            },
          };
        }

        return fields;
      },
      // ...
    },
  },
};
```

<ConfigPreview
  label='Try changing the "drink" field'
  componentConfig={{
    resolveFields: (data) => {
      const fields = {
        drink: {
          type: "radio",
          options: [
            { label: "Water", value: "water" },
            { label: "Orange juice", value: "orange-juice" },
          ],
        },
      };

      if (data.props.drink === "water") {
        return {
          ...fields,
          waterType: {
            type: "radio",
            options: [
              { label: "Still", value: "still" },
              { label: "Sparkling", value: "sparkling" },
            ],
          },
        };
      }

      return fields;
    },
    defaultProps: {
      drink: "water",
      waterType: "still",
    },
    render: ({ drink, waterType }) => (
      <p>
        {drink}
        {drink === "water" ? ` (${waterType})` : ""}
      </p>
    ),

}}
/>

#### Args

| Prop     | Example                                                                               | Type   |
| -------- | ------------------------------------------------------------------------------------- | ------ |
| `data`   | `{ props: { title: "Hello, world" }, readOnly: {} }`                                  | Object |
| `params` | `{ appState: {}, changed: {}, fields: {}, lastData: {}, lastFields: {}, parent: {} }` | Object |

##### `data.props`

The current props for the selected component.

##### `data.readOnly`

The fields currently set to read-only for this component.

##### `params.appState`

An object describing the [AppState](/docs/api-reference/data-model/app-state).

##### `params.changed`

An object describing which props have changed on this component since the last time this function was called.

```tsx copy {2-4} /changed/1 filename="Example only updating the fields when 'fieldType' changes"
const resolveFields = async ({ props }, { changed, lastFields }) => {
  if (!changed.fieldType) {
    return lastFields;
  }

  return {
    title: {
      type: fieldType,
    },
  };
};
```

##### `params.fields`

The static fields for this component as defined by [`fields`](#fields).

##### `params.lastData`

The data object from the previous run of this function.

##### `params.lastFields`

The last fields object created by the previous run of this function.

##### `params.metadata`

An object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).

##### `params.parent`

The parent data object if this item is within a [DropZone](/docs/api-reference/components/drop-zone).

### `resolvePermissions(data, params)`

Dynamically set the [permissions](/docs/api-reference/permissions) for this component to toggle functionality. Can be used to control the permissions on a specific component instance. Inherits [`permissions`](#permissions). Supports asynchronous calls.

This function is triggered when [`<Puck>`](/docs/api-reference/components/puck) renders, when the component is inserted, when its data changes, or when it's moved to a different parent.

```tsx {4-12} copy filename="Setting permissions for a specific instance" showLineNumbers
const config = {
  components: {
    MyComponent: {
      resolvePermissions: (data, { permissions }) => {
        if (data.props.id === "MyComponent-1234") {
          return {
            delete: false, // Disable deletion on component with id MyComponent-1234
          };
        }

        return { permissions }; // Fallback to inherited permissions
      },
      // ...
    },
  },
};
```

#### Args

| Prop     | Example                                                                                         | Type   |
| -------- | ----------------------------------------------------------------------------------------------- | ------ |
| `data`   | `{ props: { title: "Hello, world" }, readOnly: {} }`                                            | Object |
| `params` | `{ appState: {}, changed: {}, permissions: {}, lastData: {}, lastPermissions: {}, parent: {} }` | Object |

##### `data.props`

The current props for the selected component.

##### `data.readOnly`

The fields currently set to read-only for this component.

##### `params.appState`

An object describing the [AppState](/docs/api-reference/data-model/app-state).

##### `params.changed`

An object describing which props have changed on this component since the last time this function was called. This helps prevent duplicate calls when making async operations.

```tsx copy {2-4} /changed/1 filename="Example only updating the permissions when 'example' prop changes"
const resolvePermissions = async ({ props }, { changed, lastPermissions }) => {
  if (!changed.example) {
    return lastPermissions; // Return the last permissions unless the `example` prop has changed
  }

  return await expensiveAsyncOperation();
};
```

##### `params.permissions`

The static fields for this component as defined by [`permissions`](#permissions).

##### `params.lastData`

The data object from the previous run of this function.

##### `params.lastPermissions`

The last permissions object created by the previous run of this function.

##### `params.parent`

The [component data](/docs/api-reference/data-model/component-data) of the parent.

If the parent implements [resolveData](#resolvedatadata-params), this parameter will return the unresolved data on initial render.

#### Returns

A [`fields`](#fields) object.
